using System;
using System.Collections;
using System.Collections.Generic;

namespace SCIENEW {
    public class BiDictionary<TFirst, TSecond> : IEnumerable<KeyValuePair<TFirst, TSecond>> {
        private IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
        private IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();

        #region Exception throwing methods

        public void Add(TFirst first, TSecond second) {
            if (firstToSecond.ContainsKey(first) || secondToFirst.ContainsKey(second))
                throw new ArgumentException("Duplicate first or second");
            firstToSecond.Add(first, second);
            secondToFirst.Add(second, first);
        }

        public TSecond GetByFirst(TFirst first) {
            if (!firstToSecond.TryGetValue(first, out TSecond second)) throw new ArgumentException("first");
            return second;
        }

        public TFirst GetBySecond(TSecond second) {
            if (!secondToFirst.TryGetValue(second, out TFirst first)) throw new ArgumentException("second");
            return first;
        }

        public void RemoveByFirst(TFirst first) {
            if (!firstToSecond.TryGetValue(first, out TSecond second)) throw new ArgumentException("first");
            firstToSecond.Remove(first);
            secondToFirst.Remove(second);
        }

        public void RemoveBySecond(TSecond second) {
            if (!secondToFirst.TryGetValue(second, out TFirst first)) throw new ArgumentException("second");
            secondToFirst.Remove(second);
            firstToSecond.Remove(first);
        }

        #endregion

        #region Try methods

        public bool TryAdd(TFirst first, TSecond second) {
            if (firstToSecond.ContainsKey(first) || secondToFirst.ContainsKey(second))
                return false;
            firstToSecond.Add(first, second);
            secondToFirst.Add(second, first);
            return true;
        }

        public bool TryGetByFirst(TFirst first, out TSecond second) {
            return firstToSecond.TryGetValue(first, out second);
        }

        public bool TryGetBySecond(TSecond second, out TFirst first) {
            return secondToFirst.TryGetValue(second, out first);
        }

        public bool TryRemoveByFirst(TFirst first) {
            if (!firstToSecond.TryGetValue(first, out TSecond second)) return false;
            firstToSecond.Remove(first);
            secondToFirst.Remove(second);
            return true;
        }

        public bool TryRemoveBySecond(TSecond second) {
            if (!secondToFirst.TryGetValue(second, out TFirst first)) return false;
            secondToFirst.Remove(second);
            firstToSecond.Remove(first);
            return true;
        }

        #endregion

        public int Count {
            get { return firstToSecond.Count; }
        }

        public void Clear() {
            firstToSecond.Clear();
            secondToFirst.Clear();
        }

        public bool ContainsKey(TFirst first) {
            return firstToSecond.ContainsKey(first);
        }

        public bool ContainsValue(TSecond second) {
            return secondToFirst.ContainsKey(second);
        }

        #region Indexers

        public TSecond this[TFirst first] {
            get {
                if (!firstToSecond.TryGetValue(first, out TSecond second)) throw new ArgumentException("first");
                return second;
            }
            set {
                if (firstToSecond.ContainsKey(first)) {
                    TSecond oldSecond = firstToSecond[first];
                    secondToFirst.Remove(oldSecond);
                }
                if (secondToFirst.ContainsKey(value)) {
                    TFirst oldFirst = secondToFirst[value];
                    firstToSecond.Remove(oldFirst);
                }
                firstToSecond[first] = value;
                secondToFirst[value] = first;
            }
        }

        public TFirst this[TSecond second] {
            get {
                if (!secondToFirst.TryGetValue(second, out TFirst first)) throw new ArgumentException("second");
                return first;
            }
            set {
                if (secondToFirst.ContainsKey(second)) {
                    TFirst oldFirst = secondToFirst[second];
                    firstToSecond.Remove(oldFirst);
                }
                if (firstToSecond.ContainsKey(value)) {
                    TSecond oldSecond = firstToSecond[value];
                    secondToFirst.Remove(oldSecond);
                }
                secondToFirst[second] = value;
                firstToSecond[value] = second;
            }
        }

        #endregion

        public IEnumerator<KeyValuePair<TFirst, TSecond>> GetEnumerator() {
            return firstToSecond.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }
    }
}
